package main.test.SparkAPI.Baics
//import main.test.SparkAPI.Baics.Person.unapply


import scala.collection.mutable
sealed trait Label
case object LowSaturatedFatAndSugar extends Label
case object LowSaturatedFat extends Label
case object LowSugar extends Label
case object HighSaturatedFatAndSugar extends Label


case class NutritionFacts(
                           totalFat: Double /* grams */,
                           saturatedFat: Double /* grams */,
                           sugars: Double /* grams */,
                           salt: Double /* grams */
                         )


class Drink(
             name: String,
             brand: String,
             size: Double /* milliliter */) {

    val saturatedFatThreshold: Double = ???
    val sugarThreshold: Double = ???

    def analyze(drink: Drink): Label = drink.loadNutritionFacts() match {
        case NutritionFacts(_, saturatedFat, sugar, _)
            if saturatedFat < saturatedFatThreshold &&
              sugar < sugarThreshold => LowSaturatedFatAndSugar
        case NutritionFacts(_, saturatedFat, _, _)
            if saturatedFat < saturatedFatThreshold => LowSaturatedFat
        case NutritionFacts(_, _, sugar, _)
            if sugar < sugarThreshold => LowSugar
        case _ => HighSaturatedFatAndSugar
    }

    def loadNutritionFacts(): NutritionFacts = ???
    // it retrieves the data from a database or third party
}

case class WebsiteStats(
                        lastHour:Long,
                        lastDay:Long,
                        lastMonth:Long,
                        lastQuarter:Long,
                        lastYear:Long
                        /* many more fields here!*/
                      )

case class Car(model: String,
               owner: Option[PersonP],
               registrationPlate: Option[String])
case class PersonP(name: String,
                   age: Int,
                   driveingLicense: Option[String])

case class Student(id: Long,
                   name: String,
                   tutor: Option[Professor])

case class Professor(id:Long,
                     name:String,
                     assistant:Option[Assistant])
case class Assistant(id:Long, name:String)

/**
 * Suppose you need to represent hte concept of a debit card in your a
 * application. It must contain the name of it's owner it number and its security code.
 * must ensure never to expose its owner name and security code.
 * How would you implement it so that your respect its business requiremnts, but
 * without using the opportunity to pattern match on it.
 */

case class MyStats(lastDay:Long, lastMonth:Long)










//def getPartSats():WebsiteStats = ???
/**
 * This file is written followed by the guide of the book name
 * Get Programming with Scala
 */
object collectionApi {


    def get3rdPartyStat():WebsiteStats = ???
    def lastDayAndMonthStats():MyStats = {
        val allStats = get3rdPartyStat()
        MyStats(allStats.lastDay, allStats.lastMonth)
    }

    /**
     * website stats using tuples.
     */
        def lastDayAndMonthStatVersionTwo():(Long,Long) = {
            /**
             * Return type is  a tuple containing two elements both
             * of type Long.
             */
            val allStats = get3rdPartyStat()
            // a tuple of two instances of type Long
            (allStats.lastDay, allStats.lastHour)

        }

    def testWebsiteApp(): Unit ={
        val stats = lastDayAndMonthStatVersionTwo()
        val (lastDay, lastMonth) = lastDayAndMonthStatVersionTwo()


    }













    def testCase():Unit = {
        val px: PersonP = PersonP("Liu",12,Some("X334"))
        // val PersonP(lastname, _, _) = px
        // unapply usage
        val maybeTuple1: Option[(String, Int, Option[String])] = PersonP.unapply(px)

        val p: Person = Person("James Bond",None," MINQIAO")
        val maybeTuple: Option[(String, Option[String], String)] = Person.unapply(p)
        if (maybeTuple.isDefined)
            maybeTuple match {
                case Some((x1,y1,z1)) =>println(x1)
            }
        else println("not defined")
    }


    def tesTdrinks():Unit={

//        val hello = new drink("hello", "yili", 400)
        val hello = new drink("hello", "yili", 400)
        println("hello" )
        println(hello.danalyze(hello))

    }



//    def lastDayAndMonthStats():WebsiteStats = {
//        val allStas = ???
//        WebsiteStats(allStas.lastDay,allStas.lastMonth)
//    }

    /**
     **  Define a tuple with three elements: the number 5, the string
     *   “Jane,” and the integer 3. Extract the second and third items and multiply them
     *
     *
     *   together using the multiplier operator “*.”
     *
     *
     */
    def myTupleApps():Unit = {
        val t1 = (5,"Jane",3)
        var x:Any = ""

        t1 match {
            case (a,b,c) => x = b*c
        }
        println("now x is "+x)
    }
//Implementing the unapply method

    /**
     *
     *how to implement unapply meth
     * The unapply function is a static method
     * defined in the companion object of a class
     * complementary to apply to extract information from a class instance
     * the compiler automatically adds an unapply method to its companion object.
A pattern matching construct uses it to determine which parameters it should consider
     *
     */
    def tupleThree():String={
            val (_,name,n) = (5,"Jane",3)
            name * n
        }

    def genTuple():Unit = {
        val (a,b,c) = ("hello",1,3)
        val (ac,bc,_) = ("hello",1,3)
        val t = ("hello",1,3)
        val arrowTuples: (Int, Int) = 1->2

        t._1
        t match {
            case (q,p,1) => "?"
//            case (q,p) => "?"
        }

        /**
         * To extract data from a tuple t:
         * 1) Pattern matching 2) Getters 3) Decomposition
         */


    }


    def carWithLicensedOwner(optCar: Option[Car]):Option[Car] =
        optCar.find { car =>
          car.owner.flatMap((_.driveingLicense)).isDefined
        }

    def ownerBelowAge(car:Car, age: Int): Option[String] =
        for {
            carOwner <- car.owner
            if carOwner.age < age
        } yield carOwner.name

    def VersonAonDriLicen(
                         optCar: Option[Car],
                         ownerName: String): Option[String] =
        optCar.flatMap{
            car =>
            car.owner.flatMap( person =>
            if (person.name ==ownerName)
                person.driveingLicense.map(_.toUpperCase)
            else None
            )
        }
    def VersonforDriLicen(
           optCar: Option[Car],
           ownerName: String): Option[String] =
        for {
            car <- optCar
            person <- car.owner
            if person.name == ownerName
            drivingLicense <- person.driveingLicense
        } yield drivingLicense.toUpperCase

    /**
     * Assume you implented a func to combine many (e.g., five
     * or more values in an ordered sequence using flatMap.
     * Can you think of any aspect of your implementation
     * that could make your code difficult to maintain and read?
     *
     *
     *
     */
    // For comprehension on option
    /**
     * discovered the flatMap func and how you can use it
     * to concatenate optional opereations.
     * Review code:
     * But it requires yo to nest many flatMap func calls.
     * if you have to chain five or more opt oper, it will look like
     * //=>xxx//
     * it refers to as rocket coding.You write so many nest oprs
     * that force you to intent you code many times, make it diff
     * to read.
     * Scala intro some sytatic sugar( an alternative syntax to simpl verbose oprs) for the map
     * and flatMap func called for-compre
     *
     *
     *
     */
    // it shows you how you cna reimpl your ownerDrivingLicense func
    //using for-compr
    def reImplDriLicen(OptCar: Option[Car]): Option[String] =
        for {
            car <- OptCar
            person <- car.owner
            drivingLicense <- person.driveingLicense
        } yield  drivingLicense

    /**
     * Car, person String Option
     * your alse encoutered a new keyword ,yield, which returns
     * value you cna compute using the extracted value
     * wrapped into Option.
     * It returns the value of driving License as option value.
     * as soon as for-compr finds an absent opt value, it evaluates
     * the entire expr as None.
     * You can use for-compr on very class with flatMap funct.
     *
     */

    /**
     *
     */
    def f(n:Int):Option[Int] =
        if (n<5) Some(n*2)
        else None


    def foo(OptA: Option[Int]): Option[Int] =
        for{
            a <- OptA
            b <- f(a)
            c <- Some(5*b)

        } yield c





    /**
     * Retrieve the name of the tutor of a given student.
     */
    def RetNameTuSt(stuOpt:Option[Student]):Option[String] =
        stuOpt.flatMap(student =>
          student.tutor.flatMap(tutor =>
          Some(tutor.name)
          )
        )

    /**
     * Find the ID of a professor’s
     * assistant who is tutoring a given student
     */
    def IdPrAsisStu(stuOpt:Option[Student]):Option[Long] =
        stuOpt.flatMap(student =>
          student.tutor.flatMap(tutor =>
            tutor.assistant.flatMap(assistant =>
            Some(assistant.id)
            )
          )
        )

    /**
     * Return a given
     * student only if they have a tutor with a given ID
     */
    def StuWiPrId(stuOpt:Option[Student],id:Long):Option[Student] =
        stuOpt.flatMap(student =>
          student.tutor.flatMap( tutor =>
            if (id == tutor.id) Some(student) else None
          )
        )




    /**
     * using for-comprehension is an  alternative way.
     *
     */
    def ownerDrivingLicenseRe(optCar:Option[Car]): Option[String] =
        optCar.flatMap( car =>
            car.owner.flatMap ( person  =>
                person.driveingLicense
                )
        )
//        optCar.flatMap { car =>
//        car.owner.flatMap { person  =>
//          person.driveingLicense
//        }
//        }


    def ownerDrivingLicense(car:Car): Option[String] =
        car.owner.flatMap(_.driveingLicense)
//        car.owner.map(_.driveingLicense).flatten



    def extractRegistrationPlate(car:Car):Option[String] =
        car.registrationPlate.map(plate=>plate.toUpperCase)

    def ownerName(car:Car):Option[String]=
        car.owner match {
            case Some(p) => Some(p.name)
            case None => None
        }

//        You can rewrite this function using
//        the function map rather than pattern matching
    def ownerNameRe(car:Car):Option[String] =
        car.owner.map(p=>p.name)






    def PrintYourName(person:Person):String =
        person match {
            case Person(firstName, None, lastName) => firstName+lastName
            case Person(firstName, midName, lastName) => firstName+midName.get+lastName

        }


    val log:PartialFunction[Int, Double] =
        {case x if x>0 => Math.log(x)}

    /**
     * define a total function that returns an optinal value whcih will
     * protect your code from an unepected MATCHerror EXCEPTION at runtime.
     * for example
     */
    def relog(x:Int):Option[Double] = x match {
        case x if x>0 => Some(Math.log(x))
        case _ => None
    }

    /**
     * Think in scala : class vs. type
     * they are synonymous for simplicity
     * not always the case
     * A class is a code element with a particular behavior
     * that you can instantiate through constructor.
     * A type uniquely identifies a much broader category of items
     * you can use in your programs.LETS use the REPL to see a few examples in action
     */
    def CLASSVSTYPE():Unit={
        /**
         *  scala> classOf[String]
            res0: Class[String] = class java.lang.String

            scala> typeOf[String]
            res1: reflect.runtime.universe.Type = String

         */
        import scala.reflect.runtime.universe._
        println(classOf[String])
        println(typeOf[String])


    }

    def greetingsUDF(message:Option[String]):String =
        message match {
            case Some(x) => x
            case None =>  "Greetings, Human!"
        }

    def filterudf(text:String,word:String):Option[String]={
        if (text.contains(word)) Some(text) else None
    }

    /**
     * Here Some have extend Option, so the returned value Some is
     * reasonable.
     */
    def sqrt(n:Int): Option[Double] =
        if (n>=0) Some(Math.sqrt(n)) else None

    def sqrtOrZero(n:Int):Double =
        sqrt(n) match {
            // sqrt(n) returns a value with type Option[Double]
            case Some(result) => result
            //if value is missing return 0. if the value is present, return it.
            case None => 0
        }




    /**
     *  About Option
     *
     *  1 represent a nullable value using option
     *
     *  2 use pattern matching on instances of the type Option
     *  Using null to represent nullable or missing values in
     *  Scala is an antiPattern; Use the type Option instead.
     *
     *  The type Option ensures that you deal with both the presence or the absence of an
     *  element.
     *
     *  Thanks to the Option type, you can make your system safer by avoiding nasty
     *
     *  NullPointerExceptions at runtime.Yur code will also be cleaner as you
     *  will no longer preventively check for null values;
     *  You will be able to clearly mark nullable values and act accordingly on when effectively
     *  The concept of and optional type is not  exclusive to Scala.
     *  if you are familiar with another language's Option type,
     *  such as Java, you will recoginze a few similarities between.
     *  You will analyze the structure of the Option type. YOu'll also discover how to create optional values
     *  and analyze them using pattern matching. In
     *  capstone, use Option to represent that a
     *  winner for the game"Paper Rock,Scissors" may be missing in case of a tie.
     *
     */
    def testOpt():Unit = {
        /**
         * Suppose you defined the following function to calculate the square root of
         * an integer:
         * {{{
         *   def sqrt(n:Int):Double =
         *      if(n>=0) Math.sqrt(n) else null
         * }}}
         * Its signature(what function does) does not provide any information
         * about its return value being nullable: have to look at
         * its implementation(how function computes the value) and remember
         * to deal with a potentially null value. This approach i sparticularly
         * prone to errors as you can easily forget ot handle the null case,
         * causing a NullPointerException at runtime, and it forces you to write a lot defensive
         * code to protect against null:
         * {{{
         * val x:Int = ???
         * val result = sqrt(x)
         * if (result ==null){
         *      //protect from null here
         * }
         * else{
         *      //do things here
         *     }
         * }}}
         *
         *
         *
         *
         *
         *
         *
         *
         */
        //Creating an option
        val optionInt: Option[Int]  = None

        /**
         * None is an instance of the class Option,
         * it represents a missing nullable value. Nothing is a type that you can associate with
         * every other Scala  type.
         * The term null is a keyword of the language to indicate a missing
         * reference to an object.
         *
         */
        println(optionInt)
        //test sqrt func
//        An Option[A] is either a Some[A] of a value a, or None, which
//        represents its absence.
        println(sqrt(9).get)
        val s = sqrt(23)
        if (s!=None) {println (s"s = ${s}")}
        // error
        // println("log ",this.log(-2))

        //Patter matching on optio
        /**
         * after looking at the structure of an option ,
         * let's see how you can handlle it.
         * In lesson 13, you learned that when pattern matching on
         * a sealed item the compiler will
         * warn you if you haven't considered all its possible implementations. as
         * this could cause a MatchError exception. In lesson 19.
         * you also saw how to pattern match case classes and case objects. Let's
         * put everything together and see how you can pattern match on
         * an optional value.
         * When handling an optional value, one possibility is to use pattern matching
         * to consider both the presence and the absence of a value.
         *
         * Note that pattern matching is not the noly way to handle an optional
         * value.In next lesson, you will discover how to achieve the same result using
         * higher order functions such as map and flatMap.
         */
        println("pattern matching on option test ", greetingsUDF(Some("hello scala")))
        println("pattern matching on option test ", greetingsUDF(None))

        val person = Person("Liu",None,"Minqiao")
        val person2 = Person("Liu",Some(" lllp "),"Minqiao")
        println(PrintYourName(person2))

        /**
         * Transform an element contained in an Option using the map Op
         * Simplifiy a nested optional structure using flatten
         * Chain optional values together using flatMap
         * some of its ops are recurrent, class Opt offers you a set of higher
         * order functions for them to be more productive, dont have
         * use pattern matching every time.
         * This less introduce you to some most common and useful predefined functions on
         * Opt. how to transform an opt value using map, using flatten to simplify
         * a nested optional structure.how to combine opt vlaue in an order
         * sequence using flatMap. These funct desci patterns common to many scala type other than opt.
         * understanding them is key to them, as you will  meet them in many
         * contexts.you can use func map on opt to extract data from winner if any
         * of a game.
         *
         */
        // transForming an opt
        /**
         * start by how to trans the content of opt value using map flatten
         * and flatMap methods
         * Suppose you need to represent the following case involving a car and its owner:
         * A car has a model, it may have an owner an a registration plat.
         * person has a name , age and may have a driver license.
         * car may have owener(itis brand new_
         * car may hvae no regis plate if owner hasnt registered the vehichle
         * with local authorities yet.
         * person without a riving license is still entitled to purchase a car.
         * use case class car and person to translate the req.
         */

        /**
         * The map on Opt
         * {{{
         *   def map[B](f: A => B): Option[B] =
         *      this match {
         *      case Some(a) => Some(f(a))
         *      case None => None
         *      }
         * }}}
         */
        val car = Car("benzh",None,Some("ax100z"))
        println("current plate is ",extractRegistrationPlate(car).get)

        /**
         * flatten func
         * you need to retrieve the driver's license of an owner of an car
         * if there is one, you have do :
         * {{{
         *   def ownerDrivingLicense(car: Car): Option[Option[String]] =
         *          car.owner.map(_.drivingLicense)
         * }}}
         */

        // Working with option: for-comprehension


        /**
         * Chain optinal values together sing for-comprehesion
         * Introducing conditions within for-comprehension constructs
         * Code using the most common operations defined on Option
         * Combing instances of option thanks to new type of
         * constuct called for-comprehesion.
         * how to integrate Boolean condis to control
         * how values are chained together.
         * discover other opers impl for opt, such as isDefined,
         * getOrElse,find, exists.
         * use func getOrElse on Opt to provide
         * an altern message for your HTTP req when a draw of game occurs.
         *
         *
         *
         *
         */

        /**
         * Other opers on Opt
         * The func map flatten, flatMap are some of teh available oprs you can perform
         * on an opt value. Other commly used func defined for an instance
         * of Opt[A] are as follows:
         *  a isDefined return true if an opt instance has a value, false otherwise
         *      def isDefined: Boolean
         *          Some(1) isDefined // true
         *          None.isDefined // false
         *  b func isEmpty is the opposite of isDefined: true for optional istance absent
         *  false otherwise
         *      def isEmpty : Boolean
         *      Some(1).isEmpty // false
         *      None.isEmpty // true
         *
         *  c getOrElse return the optinal value if value is present;
         *  otherwise,it will execute the provide default oper.
         *      def getOrElse(default:A):A
         *      Some(1).getOrElse(-1)  // 1
         *      None.getOrElse(-1) // -1
         *
         *  d find returns an opt value if its element satisfies a
         *     given predicate.
         *     def find(predicate:A=> Boolean):Option[A]
         *     Some(10).find(_>5) // returns Some(10)
         *     Some(1).find(_>5) // None
         *     None.find(_>5) // None
         *
         *
         * e func exists combines find with isDefined: returns
         * true if the value is present and satisfies a given predicate,
         * false otherwise:
         *          def exists(predicate:A=>Boolean) :Boolean
         *       Some(10).exist(_>5) // return true
         *       Some(1).exists(_>5)// false
         *       None.exists(_>5) // false
         *
         *
         *
         */
        // Think in Scala Dont use the func get on Opt
        /**
         * Opt has an implementation for a fun called get,
         * which return the value if present and throws a java.util.NoSuchElementException
         * if absent.Because it throws an exception, you should think it unsafe
         * to use and an antipattern because the compiler wil
         * no longer be able to guarantee that your implementation does
         * not throw exception at runtime.
         * Dont use func get on Opt.When tempted to do so,
         * ask yourself that type should be optoinal in the firstpalce.
         * Maybe should consider resolving the optional wrapper using pattern matching
         * need to reevaluate the oprs you are perfoming on an opt value:
         * E.G.
         * def foo(a: Opt[Int]):Int = if (a.isDefined) a.get else 0
         * would be best if you rewrote it using getOrElse opr on opt as
         * follows:
         *          def foo(a:Option[Int]):Int = a.getOrElse(0)
         */

        println(foo(Some(1)))
        println(foo(Some(5)))
        println(foo(None))


    }


    /**
     * Group elements using tuple
     * retrieve data from tuples
     * Extraact infor form instance of a class using the unapply metho
     * how to handle nullable values using opt.
     * In this less, learn about tupes, one of the most basic data
     * struc Scala offers to quickly group
     * data in a given order. Then comibin what you have seen
     * about tuples and type Opt to discuss the unaplly meth, the
     * func unapply is complementary to apply: you use the appply func
     * to create a class instance and unapply to extract infor from it.
     * Pattern matching is one of most powerful tools
     * you can use. So far have seen that you can pattern match
     * over raw values
     * e.g. String integers doubles objects
     * and case classes. Defining an unapply method for a class can be able to
     * pattern match on them. use tuple to group datatog
     * you'll define unapply methods to pattern match over classes without exposing sensitive infomation
     *
     */

    def testTuple():Unit = {
        //Suppose you need to repre the concept of a debit
        // card in your app . must contain the name of its owner,number
        // its security code . ensure never to expose its owners name and sec code.
        //without using opportu to pattern match on it?
        println(tupleThree())
        println("test drink")
        tesTdrinks()
        println("test drink over!!")

        testCase()

        myTupleApps()


    }

    def testList(): Unit = {
        /**
         * How to code data with immutable structures and
         * handle nullable values using the option
         * type. You will use the functionalities of the
         * List collection to query a publicly accessible data set called
         * The Movies dataset on more than 45000 films. In particular,
         * you are going to learn about the following subjects:
         *
         * * Lesson27 teaches you how to define an instance of list and add elements to it
         *      You will also see how to use pattern matching to
         *      traverse it and manipulate its items.
         * * Lesson28 shows you how to transform its element and  chain multiple sequences using
         *      the map, flatten and flatMap functions.
         * * lesson29 demonstrates how you cna query on a list's fundaental properties,
         *      such as its size or characteristics of its items.
         * * Lesson 30 teaches you different strategies to select a single element
         *      from it . Your are going to discover how to pick one, either by its position or by its features.
         *      You are also going to find the minimum / maximum items based o specific criteria.
         * * Lesson 31 shows you how to filter elements of a list either by position or by feature.
         *      You are going to see how to remove duplicates from a sequence.
         * * Lesson 32 introduces yo to different sorting strategies for a list and how to
         *      produce a human-readable representation for it. You are also going to
         *      learn how to group elements per feature and how to sum numerical sequences
         * Finally, you'll use all the operations you discover on List to query
         *      a movie data set and display your results in a consistent and human-readable manner in Lesson 33.
         * After leaning about the List collection, you'll continue with unit six, in which you'll
         * discover other useful collections in the standard Scalla collection and different strategies of error handling.
         *
         *
         *
         *
         */



    }





  def main(args: Array[String]): Unit = {
val switched = false
if (switched){

    /**
     * Seq usage
     *
     */

    var x: Seq[Int] = Seq(1, 2, 3, 7)
    val y: Seq[Int] = Seq(1,2,3,5)
    //    var z: mutable.Seq[mutable.Seq[Int]] = scala.collection.mutable.Seq[scala.collection.mutable.Seq[Int]](scala.collection.mutable.Seq(1,2,3,5))
    var t: mutable.Seq[Int] = scala.collection.mutable.Seq(1,2,3)
    var z: mutable.Seq[Int] = scala.collection.mutable.Seq(1,2,3)
    z = z++:t
    x = x ++:y


    /**
     *  List(1, 2, 3, 7, 1, 2, 3, 5)
     * ArrayBuffer(1, 2, 3, 1, 2, 3)
     * List(1, 2, 3, 5)
     * ArrayBuffer(1, 2, 3)
     */

    println(x)
    println(z)
    println(x.intersect(y))
    println(z.intersect(y))
    println("================")
    /**
     * lIST USAGE
     */
    var ints = List(1, 2, 3, 5, 7)
    var int2 = List(1, 2, 3, 5, 7)
    val int3 = List(2,3)
    //error!
    /**
     *  如果是用val定义的，即使使用mutable集合，
     *  也只能对集合的元素进行修改
     *  而不能让变量指向另一个集合
     *
     */

    //    int3 = int3 ++: List(1)
    println(" this ok ",int3 ++: List(1))
    //
    var mutable0 = collection.mutable.Set(0)
    z = z ++ collection.mutable.Seq(12)
    mutable0+=13
    println("updated mtable0 ",mutable0,z)

    ints = ints++:int2

    println(ints)





}

      testOpt()
//CLASSVSTYPE()
      testTuple()

      val helo = new drink("helo", "world", 2.3)



  }



}

